PluginEditor = function (jsEditor, valueEditor) {
  function _displayValidationError(settingName, errorMessage) {
    var errorElement = $('<div class="validation-error"></div>').html(errorMessage);
    $("#setting-value-container-" + settingName).append(errorElement);
  }

  function _removeSettingsRows() {
    if ($("#setting-row-instance-name").length) {
      $("#setting-row-instance-name").nextAll().remove();
    }
    else {
      $("#setting-row-plugin-types").nextAll().remove();
    }
  }

  function _isNumerical(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
  }

  function _appendCalculatedSettingRow(valueCell, newSettings, settingDef, currentValue, includeRemove) {
    var input = $('<textarea></textarea>');

    if (settingDef.multi_input) {
      input.change(function () {
        var arrayInput = [];
        $(valueCell).find('textarea').each(function () {
          var thisVal = $(this).val();
          if (thisVal) {
            arrayInput = arrayInput.concat(thisVal);
          }
        });
        newSettings.settings[settingDef.name] = arrayInput;
      });
    } else {
      input.change(function () {
        newSettings.settings[settingDef.name] = $(this).val();
      });
    }

    if (currentValue) {
      input.val(currentValue);
    }

    valueEditor.createValueEditor(input);

    var datasourceToolbox = $('<ul class="board-toolbar datasource-input-suffix"></ul>');
    var wrapperDiv = $('<div class="calculated-setting-row"></div>');
    wrapperDiv.append(input).append(datasourceToolbox);

    var datasourceTool = $('<li><i class="icon-plus icon-white"></i><label>DATASOURCE</label></li>')
      .mousedown(function (e) {
        e.preventDefault();
        $(input).val("").focus().insertAtCaret("datasources[\"").trigger("freeboard-eval");
      });
    datasourceToolbox.append(datasourceTool);

    var jsEditorTool = $('<li><i class="icon-fullscreen icon-white"></i><label>.JS EDITOR</label></li>')
      .mousedown(function (e) {
        e.preventDefault();
        jsEditor.displayJSEditor(input.val(), function (result) {
          input.val(result);
          input.change();
        });
      });
    datasourceToolbox.append(jsEditorTool);

    if (includeRemove) {
      var removeButton = $('<li class="remove-setting-row"><i class="icon-minus icon-white"></i><label></label></li>')
        .mousedown(function (e) {
          e.preventDefault();
          wrapperDiv.remove();
          $(valueCell).find('textarea:first').change();
        });
      datasourceToolbox.prepend(removeButton);
    }

    $(valueCell).append(wrapperDiv);
  }

  function createPluginEditor(title, pluginTypes, currentTypeName, currentSettingsValues, settingsSavedCallback) {
    var newSettings = {
      type: currentTypeName,
      settings: {}
    };

    function createSettingRow(name, displayName) {
      var tr = $('<div id="setting-row-' + name + '" class="form-row"></div>').appendTo(form);

      tr.append('<div class="form-label"><label class="control-label">' + displayName + '</label></div>');
      return $('<div id="setting-value-container-' + name + '" class="form-value"></div>').appendTo(tr);
    }

    var selectedType;
    var form = $('<div></div>');

    var pluginDescriptionElement = $('<div id="plugin-description"></div>').hide();
    form.append(pluginDescriptionElement);

    function createSettingsFromDefinition(settingsDefs, typeaheadSource, typeaheadDataSegment) {
      _.each(settingsDefs, function (settingDef) {
        // Set a default value if one doesn't exist
        if (!_.isUndefined(settingDef.default_value) && _.isUndefined(currentSettingsValues[settingDef.name])) {
          currentSettingsValues[settingDef.name] = settingDef.default_value;
        }

        var displayName = settingDef.name;

        if (!_.isUndefined(settingDef.display_name)) {
          displayName = settingDef.display_name;
        }

        var valueCell = createSettingRow(settingDef.name, displayName);

        switch (settingDef.type) {
          case "array": {
            var subTableDiv = $('<div class="form-table-value-subtable"></div>').appendTo(valueCell);

            var subTable = $('<table class="table table-condensed sub-table"></table>').appendTo(subTableDiv);
            var subTableHead = $("<thead></thead>").hide().appendTo(subTable);
            var subTableHeadRow = $("<tr></tr>").appendTo(subTableHead);
            var subTableBody = $('<tbody></tbody>').appendTo(subTable);

            var currentSubSettingValues = [];

            // Create our headers
            _.each(settingDef.settings, function (subSettingDef) {
              var subsettingDisplayName = subSettingDef.name;

              if (!_.isUndefined(subSettingDef.display_name)) {
                subsettingDisplayName = subSettingDef.display_name;
              }

              $('<th>' + subsettingDisplayName + '</th>').appendTo(subTableHeadRow);
            });

            if (settingDef.name in currentSettingsValues) {
              currentSubSettingValues = currentSettingsValues[settingDef.name];
            }

            function processHeaderVisibility() {
              if (newSettings.settings[settingDef.name].length > 0) {
                subTableHead.show();
              }
              else {
                subTableHead.hide();
              }
            }

            function createSubsettingRow(subsettingValue) {
              var subsettingRow = $('<tr></tr>').appendTo(subTableBody);

              var newSetting = {};

              if (!_.isArray(newSettings.settings[settingDef.name])) {
                newSettings.settings[settingDef.name] = [];
              }

              newSettings.settings[settingDef.name].push(newSetting);

              _.each(settingDef.settings, function (subSettingDef) {
                var subsettingCol = $('<td></td>').appendTo(subsettingRow);
                var subsettingValueString = "";

                if (!_.isUndefined(subsettingValue[subSettingDef.name])) {
                  subsettingValueString = subsettingValue[subSettingDef.name];
                }

                newSetting[subSettingDef.name] = subsettingValueString;

                $('<input class="table-row-value" type="text">').appendTo(subsettingCol).val(subsettingValueString).change(function () {
                  newSetting[subSettingDef.name] = $(this).val();
                });
              });

              subsettingRow.append($('<td class="table-row-operation"></td>').append($('<ul class="board-toolbar"></ul>').append($('<li></li>').append($('<i class="icon-trash icon-white"></i>').click(function () {
                var subSettingIndex = newSettings.settings[settingDef.name].indexOf(newSetting);

                if (subSettingIndex != -1) {
                  newSettings.settings[settingDef.name].splice(subSettingIndex, 1);
                  subsettingRow.remove();
                  processHeaderVisibility();
                }
              })))));

              subTableDiv.scrollTop(subTableDiv[0].scrollHeight);

              processHeaderVisibility();
            }

            $('<div class="table-operation text-button">ADD</div>').appendTo(valueCell).click(function () {
              var newSubsettingValue = {};

              _.each(settingDef.settings, function (subSettingDef) {
                newSubsettingValue[subSettingDef.name] = "";
              });

              createSubsettingRow(newSubsettingValue);
            });

            // Create our rows
            _.each(currentSubSettingValues, function (currentSubSettingValue, subSettingIndex) {
              createSubsettingRow(currentSubSettingValue);
            });

            break;
          }
          case "boolean": {
            newSettings.settings[settingDef.name] = currentSettingsValues[settingDef.name];

            var onOffSwitch = $('<div class="onoffswitch"><label class="onoffswitch-label" for="' + settingDef.name + '-onoff"><div class="onoffswitch-inner"><span class="on">YES</span><span class="off">NO</span></div><div class="onoffswitch-switch"></div></label></div>').appendTo(valueCell);

            var input = $('<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="' + settingDef.name + '-onoff">').prependTo(onOffSwitch).change(function () {
              newSettings.settings[settingDef.name] = this.checked;
            });

            if (settingDef.name in currentSettingsValues) {
              input.prop("checked", currentSettingsValues[settingDef.name]);
            }

            break;
          }
          case "option": {
            var defaultValue = currentSettingsValues[settingDef.name];

            var input = $('<select></select>').appendTo($('<div class="styled-select"></div>').appendTo(valueCell)).change(function () {
              newSettings.settings[settingDef.name] = $(this).val();
            });

            _.each(settingDef.options, function (option) {

              var optionName;
              var optionValue;

              if (_.isObject(option)) {
                optionName = option.name;
                optionValue = option.value;
              }
              else {
                optionName = option;
              }

              if (_.isUndefined(optionValue)) {
                optionValue = optionName;
              }

              if (_.isUndefined(defaultValue)) {
                defaultValue = optionValue;
              }

              $("<option></option>").text(optionName).attr("value", optionValue).appendTo(input);
            });

            newSettings.settings[settingDef.name] = defaultValue;

            if (settingDef.name in currentSettingsValues) {
              input.val(currentSettingsValues[settingDef.name]);
            }

            break;
          }
          default: {
            newSettings.settings[settingDef.name] = currentSettingsValues[settingDef.name];

            if (settingDef.type == "calculated") {
              if (settingDef.name in currentSettingsValues) {
                var currentValue = currentSettingsValues[settingDef.name];
                if (settingDef.multi_input && _.isArray(currentValue)) {
                  var includeRemove = false;
                  for (var i = 0; i < currentValue.length; i++) {
                    _appendCalculatedSettingRow(valueCell, newSettings, settingDef, currentValue[i], includeRemove);
                    includeRemove = true;
                  }
                } else {
                  _appendCalculatedSettingRow(valueCell, newSettings, settingDef, currentValue, false);
                }
              } else {
                _appendCalculatedSettingRow(valueCell, newSettings, settingDef, null, false);
              }

              if (settingDef.multi_input) {
                var inputAdder = $('<ul class="board-toolbar"><li class="add-setting-row"><i class="icon-plus icon-white"></i><label>ADD</label></li></ul>')
                  .mousedown(function (e) {
                    e.preventDefault();
                    _appendCalculatedSettingRow(valueCell, newSettings, settingDef, null, true);
                  });
                $(valueCell).siblings('.form-label').append(inputAdder);
              }
            }
            else {
              var input = $('<input type="text">').appendTo(valueCell).change(function () {
                if (settingDef.type == "number") {
                  newSettings.settings[settingDef.name] = Number($(this).val());
                }
                else {
                  newSettings.settings[settingDef.name] = $(this).val();
                }
              });

              if (settingDef.name in currentSettingsValues) {
                input.val(currentSettingsValues[settingDef.name]);
              }

              if (typeaheadSource && settingDef.typeahead_data_field) {
                input.addClass('typeahead_data_field-' + settingDef.typeahead_data_field);
              }

              if (typeaheadSource && settingDef.typeahead_field) {
                var typeaheadValues = [];

                input.keyup(function (event) {
                  if (event.which >= 65 && event.which <= 91) {
                    input.trigger('change');
                  }
                });

                $(input).autocomplete({
                  source: typeaheadValues,
                  select: function (event, ui) {
                    input.val(ui.item.value);
                    input.trigger('change');
                  }
                });

                input.change(function (event) {
                  var value = input.val();
                  var source = _.template(typeaheadSource)({input: value});
                  $.get(source, function (data) {
                    if (typeaheadDataSegment) {
                      data = data[typeaheadDataSegment];
                    }
                    data = _.select(data, function (elm) {
                      return elm[settingDef.typeahead_field][0] == value[0];
                    });

                    typeaheadValues = _.map(data, function (elm) {
                      return elm[settingDef.typeahead_field];
                    });
                    $(input).autocomplete("option", "source", typeaheadValues);

                    if (data.length == 1) {
                      data = data[0];
                      //we found the one. let's use it to populate the other info
                      for (var field in data) {
                        if (data.hasOwnProperty(field)) {
                          var otherInput = $(_.template('input.typeahead_data_field-<%= field %>')({field: field}));
                          if (otherInput) {
                            otherInput.val(data[field]);
                            if (otherInput.val() != input.val()) {
                              otherInput.trigger('change');
                            }
                          }
                        }
                      }
                    }
                  });
                });
              }
            }

            break;
          }
        }

        if (!_.isUndefined(settingDef.suffix)) {
          valueCell.append($('<div class="input-suffix">' + settingDef.suffix + '</div>'));
        }

        if (!_.isUndefined(settingDef.description)) {
          valueCell.append($('<div class="setting-description">' + settingDef.description + '</div>'));
        }
      });
    }


    new DialogBox(form, title, "保存", "取消", function () {
      $(".validation-error").remove();

      // Loop through each setting and validate it
      for (var index = 0; index < selectedType.settings.length; index++) {
        var settingDef = selectedType.settings[index];

        if (settingDef.required && (_.isUndefined(newSettings.settings[settingDef.name]) || newSettings.settings[settingDef.name] == "")) {
          _displayValidationError(settingDef.name, "不能为空");
          return true;
        }
        else if (settingDef.type == "integer" && (newSettings.settings[settingDef.name] % 1 !== 0)) {
          _displayValidationError(settingDef.name, "必须是整数");
          return true;
        }
        else if (settingDef.type == "number" && !_isNumerical(newSettings.settings[settingDef.name])) {
          _displayValidationError(settingDef.name, "必须是数字");
          return true;
        }
      }

      if (_.isFunction(settingsSavedCallback)) {
        settingsSavedCallback(newSettings);
      }
    });

    // Create our body
    var pluginTypeNames = _.keys(pluginTypes);
    var typeSelect;

    if (pluginTypeNames.length > 1) {
      var typeRow = createSettingRow("plugin-types", "类型");
      typeSelect = $('<select></select>').appendTo($('<div class="styled-select"></div>').appendTo(typeRow));

      typeSelect.append($("<option>请选择...</option>").attr("value", "undefined"));

      _.each(pluginTypes, function (pluginType) {
        typeSelect.append($("<option></option>").text(pluginType.display_name).attr("value", pluginType.type_name));
      });

      typeSelect.change(function () {
        newSettings.type = $(this).val();
        newSettings.settings = {};

        // Remove all the previous settings
        _removeSettingsRows();

        selectedType = pluginTypes[typeSelect.val()];

        if (_.isUndefined(selectedType)) {
          $("#setting-row-instance-name").hide();
          $("#dialog-ok").hide();
        }
        else {
          $("#setting-row-instance-name").show();

          if (selectedType.description && selectedType.description.length > 0) {
            pluginDescriptionElement.html(selectedType.description).show();
          }
          else {
            pluginDescriptionElement.hide();
          }

          $("#dialog-ok").show();
          createSettingsFromDefinition(selectedType.settings, selectedType.typeahead_source, selectedType.typeahead_data_segment);
        }
      });
    }
    else if (pluginTypeNames.length == 1) {
      selectedType = pluginTypes[pluginTypeNames[0]];
      newSettings.type = selectedType.type_name;
      newSettings.settings = {};
      createSettingsFromDefinition(selectedType.settings);
    }

    if (typeSelect) {
      if (_.isUndefined(currentTypeName)) {
        $("#setting-row-instance-name").hide();
        $("#dialog-ok").hide();
      }
      else {
        $("#dialog-ok").show();
        typeSelect.val(currentTypeName).trigger("change");
      }
    }
  }

  // Public API
  return {
    createPluginEditor: function (
      title,
      pluginTypes,
      currentInstanceName,
      currentTypeName,
      currentSettingsValues,
      settingsSavedCallback) {
      createPluginEditor(title, pluginTypes, currentInstanceName, currentTypeName, currentSettingsValues, settingsSavedCallback);
    }
  }
}
